/*!
 * @author  Vladimir Kerimov
 * @file    cast.h
 * @version 1.0
 * @brief   Type cast
 * @date    2013
 */

/*
 *    ______   __
 *   /  ___/  /__/   __  ___      _____        _____     __    __     _____        __  ___      _____     __  ___
 *  |  |___    __   /  |/   \    / __  \      /  _  \   |  |  |  |   /  _  \      |  |/   \    /  _  \   |  |/   \
 *   \___  \  /  |  |   __   |  | /  __/     |  / \  |  |  |  |  |  |  / \  |     |   __   |  |  / \  |  |   __   |
 *  ____/  /  |  |  |  /  \  |  |   /___     |  \_/  |  |   \_|  |  |  \_/  |     |  /  \  |  |  \_/  |  |  /  \  |
 * /______/   |__/  |__|  |__/   \_____/      \___   |   \___/|__|   \___/|_|     |__|  |__|   \_____/   |__|  |__|
 *                                                |  |
 *                                                |__/
 *
 */

#pragma once

#include <qua/api.h>
#include <qua/stdfwd.h>
#include <typeinfo>
#include <cstdint>
#include <limits>

namespace qua
{
    class text;
    class fixed;
    class time;

    template< typename T >
    struct cast_to;

    template< typename TargetT, typename SourceT >
    TargetT cast( SourceT value )
    {
        return cast_to<TargetT>::from( value );
    }

    template< typename TargetT, typename SourceT >
    TargetT cast( SourceT value, const char* format )
    {
        return cast_to<TargetT>::from( value, format );
    }

    template<>
    struct QUAZAR_API cast_to<text>
    {
        static text from( std::nullptr_t );

        static text from( bool value );

        static text from( uint8_t  value, const char* format = nullptr );
        static text from( int8_t   value, const char* format = nullptr );
        static text from( uint16_t value, const char* format = nullptr );
        static text from( int16_t  value, const char* format = nullptr );
        static text from( uint32_t value, const char* format = nullptr );
        static text from( int32_t  value, const char* format = nullptr );
        static text from( uint64_t value, const char* format = nullptr );
        static text from( int64_t  value, const char* format = nullptr );
        static text from( double   value, const char* format = nullptr );
        static text from( float    value, const char* format = nullptr );

        static text from( fixed const& value, const char* format = nullptr );
        static text from( time  const& value, const char* format = nullptr );
    };

    template<>
    struct QUAZAR_API cast_to<bool>
    {
        static bool from( text const& value );

        template< typename T >
        static bool from( T value ) {
            return value != 0;
        }
    };

    QUAZAR_API void throw_out_of_range_error( const char* target_type_name, const char* source_type_name );

    template< typename TargetT, typename SourceT >
    TargetT cast_in_range( SourceT value, TargetT range_min, TargetT range_max )
    {
        if( value > range_max || value < range_min )
            throw_out_of_range_error( typeid(TargetT).name(), typeid(SourceT).name() );
        return static_cast<uint8_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<uint8_t>
    {
        static uint8_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static uint8_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max() );
        }
    };

    template<>
    inline uint8_t cast_to<uint8_t>::from( bool value ) {
        return static_cast<uint8_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<int8_t>
    {
        static int8_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static int8_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max() );
        }
    };

    template<>
    inline int8_t cast_to<int8_t>::from( bool value ) {
        return static_cast<int8_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<uint16_t>
    {
        static uint16_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static uint16_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max() );
        }
    };

    template<>
    inline uint16_t cast_to<uint16_t>::from( bool value ) {
        return static_cast<uint16_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<int16_t>
    {
        static int16_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static int16_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max() );
        }
    };

    template<>
    inline int16_t cast_to<int16_t>::from( bool value ) {
        return static_cast<int16_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<uint32_t>
    {
        static uint32_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static uint32_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max() );
        }
    };

    template<>
    inline uint32_t cast_to<uint32_t>::from( bool value ) {
        return static_cast<uint32_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<int32_t>
    {
        static int32_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static int32_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max() );
        }
    };

    template<>
    inline int32_t cast_to<int32_t>::from( bool value ) {
        return static_cast<int32_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<uint64_t>
    {
        static uint64_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static uint64_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max() );
        }
    };

    template<>
    inline uint64_t cast_to<uint64_t>::from( bool value ) {
        return static_cast<uint64_t>( value );
    }

    template<>
    inline uint64_t cast_to<uint64_t>::from( int64_t value ) {
        if( value < 0 )
            throw_out_of_range_error( typeid(uint64_t).name(), typeid(int64_t).name() );
        return static_cast<uint64_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<int64_t>
    {
        static int64_t from( text const& value, const char* format = nullptr );

        template< typename T >
        static int64_t from( T value ) {
            return cast_in_range( value, std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max() );
        }
    };

    template<>
    inline int64_t cast_to<int64_t>::from( bool value ) {
        return static_cast<int64_t>( value );
    }

    template<>
    inline int64_t cast_to<int64_t>::from( uint64_t value ) {
        if( (value & (1uLL << 63)) != 0 )
            throw_out_of_range_error( typeid(int64_t).name(), typeid(uint64_t).name() );
        return static_cast<int64_t>( value );
    }

    template<>
    struct QUAZAR_API cast_to<float>
    {
        static float from( text const& value, const char* format = nullptr );

        template< typename T >
        static float from( T value ) {
            return cast_in_range( value, -std::numeric_limits<float>::max(), std::numeric_limits<float>::max() );
        }
    };

    template<>
    inline float cast_to<float>::from( bool value ) {
        return static_cast<float>( value );
    }

    template<>
    struct QUAZAR_API cast_to<double>
    {
        static double from( text const& value, const char* format = nullptr );

        template< typename T >
        static double from( T value ) {
            return cast_in_range( value, -std::numeric_limits<double>::max(), std::numeric_limits<double>::max() );
        }
    };

    template<>
    inline double cast_to<double>::from( bool value ) {
        return static_cast<double>( value );
    }

    template<>
    struct QUAZAR_API cast_to<fixed>
    {
        static fixed from( text const& value, const char* format = nullptr );

        template< typename T >
        static fixed from( T value );
    };

    template<>
    struct QUAZAR_API cast_to<time>
    {
        static time from( text const& value, const char* format = nullptr );
    };

    template<> QUAZAR_API fixed cast_to<fixed>::from( bool      value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( int8_t    value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( int16_t   value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( int32_t   value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( int64_t   value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( uint8_t   value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( uint16_t  value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( uint32_t  value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( uint64_t  value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( float     value );
    template<> QUAZAR_API fixed cast_to<fixed>::from( double    value );
}
